#include "BankSystem.hh"
#include <iostream>
#include <iomanip>
#include <random>
#include <fstream>
#include <ctime>
#include <sstream>
#include <limits>
#include <regex>
#ifdef _WIN32
#include <cstdlib>
#define CLEAR_SCREEN "cls"
#else
#define CLEAR_SCREEN "clear"
#endif

namespace BankingSystem {

BankSystem::BankSystem() {
    customers.reserve(100); // Pre-allocate space for customers
    usedAccountIds.reserve(200); // Pre-allocate space for account IDs
}

// Utility function to clear input buffer
void clearInputBuffer() {
    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}

// Utility function to get valid numeric input
double getValidAmount() {
    double amount;
    while (!(std::cin >> amount) || amount <= 0 || amount > 1000000.0) {
        std::cout << "Error: Please enter a valid amount (positive and less than 1,000,000): ";
        clearInputBuffer();
    }
    clearInputBuffer();
    return amount;
}

// Utility function to get valid menu choice
int getValidChoice(int maxChoice) {
    int choice;
    while (!(std::cin >> choice) || choice < 1 || choice > maxChoice) {
        std::cout << "Error: Please enter a number between 1 and " << maxChoice << ": ";
        clearInputBuffer();
    }
    clearInputBuffer();
    return choice;
}

// Utility function to get valid customer name
std::string getValidName() {
    std::string name;
    while (true) {
        std::getline(std::cin, name);
        if (name.empty() || name.length() > 50) {
            std::cout << "Error: Name must be non-empty and less than 50 characters: ";
            continue;
        }
        break;
    }
    return name;
}

// Utility function to clear screen
void clearScreen() {
    std::system(CLEAR_SCREEN);
}

std::string BankSystem::generateAccountId(const std::string& accountType) const {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> branchDist(1000, 9999);
    std::uniform_int_distribution<> accountDist(1000, 9999);

    std::stringstream ss;
    ss << "62" << (accountType == "Savings" ? "21" : "22") << "-"
       << std::setw(4) << std::setfill('0') << branchDist(gen) << "-"
       << std::setw(4) << std::setfill('0') << accountDist(gen) << "-"
       << std::setw(4) << std::setfill('0') << accountDist(gen);

    std::string accountId = ss.str();
    if (!isUniqueAccountId(accountId)) {
        return generateAccountId(accountType);
    }
    return accountId;
}

bool BankSystem::isUniqueAccountId(const std::string& accountId) const {
    return std::find(usedAccountIds.begin(), usedAccountIds.end(), accountId) == usedAccountIds.end();
}

bool BankSystem::validateAccountId(const std::string& accountId) const {
    std::regex pattern("^62(21|22)-[0-9]{4}-[0-9]{4}-[0-9]{4}$");
    return std::regex_match(accountId, pattern);
}

Customer* BankSystem::findCustomer(const std::string& name) const {
    for (const auto& customer : customers) {
        if (customer->getName() == name) {
            return customer.get();
        }
    }
    return nullptr;
}

BankAccount* BankSystem::findAccount(const std::string& accountId) const {
    for (const auto& customer : customers) {
        if (customer->getSavingsAccount()->getAccountId() == accountId) {
            return customer->getSavingsAccount();
        }
        if (customer->getCheckingAccount()->getAccountId() == accountId) {
            return customer->getCheckingAccount();
        }
    }
    return nullptr;
}

void BankSystem::registerCustomer(const std::string& name) {
    if (name.empty() || name.length() > 50) {
        std::cout << "Error: Customer name must be non-empty and less than 50 characters.\n";
        return;
    }
    if (findCustomer(name)) {
        std::cout << "Error: Customer already exists.\n";
        return;
    }

    std::string savingsId = generateAccountId("Savings");
    std::string checkingId = generateAccountId("Checking");

    customers.push_back(std::make_unique<Customer>(name, savingsId, checkingId));
    usedAccountIds.push_back(savingsId);
    usedAccountIds.push_back(checkingId);

    std::cout << "Customer " << name << " successfully registered!\n";
    std::cout << "Two bank accounts have been created:\n";
    std::cout << "- Savings Account: " << savingsId << "\n";
    std::cout << "- Checking Account: " << checkingId << "\n";
}

void BankSystem::displayAllAccounts() const {
    double totalBalance = 0.0;
    int totalAccounts = 0;

    std::cout << "===== All Bank Accounts =====\n\n";
    std::cout << "SAVINGS ACCOUNTS:\n";
    int index = 1;
    for (const auto& customer : customers) {
        auto* account = customer->getSavingsAccount();
        std::cout << std::left << std::setw(3) << index++ << ". ID: " << std::setw(20) << account->getAccountId()
                  << " | Owner: " << std::setw(20) << account->getOwnerName()
                  << " | Balance: " << std::fixed << std::setprecision(2) << account->getBalance() << "\n";
        totalBalance += account->getBalance();
        totalAccounts++;
    }

    std::cout << "\nCHECKING ACCOUNTS:\n";
    index = 1;
    for (const auto& customer : customers) {
        auto* account = customer->getCheckingAccount();
        std::cout << std::left << std::setw(3) << index++ << ". ID: " << std::setw(20) << account->getAccountId()
                  << " | Owner: " << std::setw(20) << account->getOwnerName()
                  << " | Balance: " << std::fixed << std::setprecision(2) << account->getBalance() << "\n";
        totalBalance += account->getBalance();
        totalAccounts++;
    }

    std::cout << "\nTotal Accounts: " << totalAccounts << "\n";
    std::cout << "Total Customers: " << customers.size() << "\n";
    std::cout << "Total Balance: " << std::fixed << std::setprecision(2) << totalBalance << "\n";
}

bool BankSystem::generateGlobalReport() const {
    std::time_t now = std::time(nullptr);
    std::stringstream ss;
    ss << std::put_time(std::localtime(&now), "transactions_%Y-%m-%d.txt");
    std::string filename = ss.str();

    std::ofstream file(filename);
    if (!file.is_open()) {
        std::cout << "Error: Could not create report file: " << filename << "\n";
        return false;
    }

    file << "Global Transaction Report\n\n";
    for (const auto& customer : customers) {
        file << "Customer: " << customer->getName() << "\n";
        file << "Savings Account: " << customer->getSavingsAccount()->getAccountId() << "\n";
        file << std::left << std::setw(10) << "ID"
             << std::setw(20) << "Date"
             << std::setw(15) << "Type"
             << std::setw(10) << "Amount"
             << "Note\n";
        file << std::string(65, '-') << "\n";

        for (const auto& t : customer->getSavingsAccount()->getTransactions()) {
            file << std::left << std::setw(10) << t.getTransactionId()
                 << std::setw(20) << t.getDate()
                 << std::setw(15) << t.getType()
                << std::fixed << std::setprecision(2) << std::setw(9) << t.getAmount()
                 << t.getNote() << "\n";
        }

        file << "\nChecking Account: " << customer->getCheckingAccount()->getAccountId() << "\n";
        file << std::left << std::setw(10) << "ID"
             << std::setw(20) << "Date"
             << std::setw(15) << "Type"
             << std::setw(10) << "Amount"
             << "Note\n";
        file << std::string(65, '-') << "\n";

        for (const auto& t : customer->getCheckingAccount()->getTransactions()) {
            file << std::left << std::setw(10) << t.getTransactionId()
                 << std::setw(20) << t.getDate()
                 << std::setw(15) << t.getType()
                 << "¥" << std::fixed << std::setprecision(2) << std::setw(9) << t.getAmount()
                 << t.getNote() << "\n";
        }
        file << "\n";
    }

    file.close();
    std::cout << "Report successfully generated as " << filename << "\n";
    return true;
}

void BankSystem::run() {
    while (true) {
        clearScreen();
        std::cout << "\n===== Banking System =====\n";
        std::cout << "1. Register new customer\n";
        std::cout << "2. Access customer accounts\n";
        std::cout << "3. Display all bank accounts\n";
        std::cout << "4. Transaction reports\n";
        std::cout << "5. Exit\n";
        std::cout << "Please choose: ";

        int choice = getValidChoice(5);
        if (choice == 5) break;

        clearScreen();
        switch (choice) {
            case 1: {
                std::cout << "\n===== Register New Customer =====\n";
                std::cout << "Enter customer name: ";
                std::string name = getValidName();
                registerCustomer(name);
                std::cout << "Press Enter to continue...";
                std::cin.get();
                break;
            }
            case 2: {
                std::cout << "\n===== Access Customer Accounts =====\n";
                std::cout << "Enter customer name: ";
                std::string name = getValidName();
                Customer* customer = findCustomer(name);
                if (!customer) {
                    std::cout << "Error: Customer not found.\n";
                    std::cout << "Press Enter to continue...";
                    std::cin.get();
                    break;
                }

                while (true) {
                    clearScreen();
                    std::cout << "\n===== Customer: " << name << " =====\n";
                    std::cout << "This customer has the following accounts:\n\n";
                    std::cout << "1. Savings Account: " << customer->getSavingsAccount()->getAccountId() << "\n";
                    std::cout << "   Balance: " << std::fixed << std::setprecision(2)
                              << customer->getSavingsAccount()->getBalance() << "\n\n";
                    std::cout << "2. Checking Account: " << customer->getCheckingAccount()->getAccountId() << "\n";
                    std::cout << "   Balance: " << std::fixed << std::setprecision(2)
                              << customer->getCheckingAccount()->getBalance() << "\n\n";
                    std::cout << "Options:\n";
                    std::cout << "1. Access Savings Account\n";
                    std::cout << "2. Access Checking Account\n";
                    std::cout << "3. View all transactions for this customer\n";
                    std::cout << "4. Generate customer report\n";
                    std::cout << "5. Return to main menu\n";
                    std::cout << "Please choose: ";

                    int subChoice = getValidChoice(5);
                    if (subChoice == 5) break;

                    clearScreen();
                    BankAccount* account = nullptr;
                    if (subChoice == 1) account = customer->getSavingsAccount();
                    else if (subChoice == 2) account = customer->getCheckingAccount();
                    else if (subChoice == 3) {
                        std::cout << "\nCustomer Transactions:\n";
                        std::cout << "Savings Account:\n";
                        std::cout << std::left << std::setw(10) << "ID"
                                  << std::setw(20) << "Date"
                                  << std::setw(15) << "Type"
                                  << std::setw(10) << "Amount"
                                  << "Note\n";
                        std::cout << std::string(65, '-') << "\n";

                        for (const auto& t : customer->getSavingsAccount()->getTransactions()) {
                            std::cout << std::left << std::setw(10) << t.getTransactionId()
                                      << std::setw(20) << t.getDate()
                                      << std::setw(15) << t.getType()
                                      << std::fixed << std::setprecision(2) << std::setw(9) << t.getAmount()
                                      << t.getNote() << "\n";
                        }

                        std::cout << "\nChecking Account:\n";
                        std::cout << std::left << std::setw(10) << "ID"
                                  << std::setw(20) << "Date"
                                  << std::setw(15) << "Type"
                                  << std::setw(10) << "Amount"
                                  << "Note\n";
                        std::cout << std::string(65, '-') << "\n";

                        for (const auto& t : customer->getCheckingAccount()->getTransactions()) {
                            std::cout << std::left << std::setw(10) << t.getTransactionId()
                                      << std::setw(20) << t.getDate()
                                      << std::setw(15) << t.getType()
                                      << std::fixed << std::setprecision(2) << std::setw(9) << t.getAmount()
                                      << t.getNote() << "\n";
                        }
                        std::cout << "Press Enter to continue...";
                        std::cin.get();
                        continue;
                    }
                    else if (subChoice == 4) {
                        std::time_t now = std::time_t(nullptr);
                        std::stringstream ss;
                        ss << "transactions_" << name << "_" << std::put_time(std::localtime(&now), "%Y-%m-%d") << ".txt";
                        customer->generateReport(ss.str());
                        std::cout << "Press Enter to continue...";
                        std::cin.get();
                        continue;
                    }

                    while (true) {
                        clearScreen();
                        std::cout << "\n===== Bank Account: " << account->getAccountId() << " (" << account->getAccountType() << ") =====\n";
                        std::cout << "Account holder: " << account->getOwnerName() << "\n";
                        std::cout << "Balance: " << std::fixed << std::setprecision(2) << account->getBalance() << "\n\n";
                        std::cout << "1. Deposit\n";
                        std::cout << "2. Withdraw\n";
                        std::cout << "3. Transfer\n";
                        std::cout << "4. View transaction history\n";
                        std::cout << "5. Generate bank account report\n";
                        std::cout << "6. Return to customer page\n";
                        std::cout << "Please choose: ";

                        int accountChoice = getValidChoice(6);
                        if (accountChoice == 6) break;

                        clearScreen();
                        switch (accountChoice) {
                            case 1: {
                                std::cout << "\n===== Deposit to Account: " << account->getAccountId() << " =====\n";
                                std::cout << "Current Balance: " << std::fixed << std::setprecision(2) << account->getBalance() << "\n";
                                std::cout << "Enter amount to deposit: ";
                                double amount = getValidAmount();
                                std::cout << "Add note (optional): ";
                                std::string note;
                                std::getline(std::cin, note);
                                account->deposit(amount, note);
                                std::cout << "Press Enter to continue...";
                                std::cin.get();
                                break;
                            }
                            case 2: {
                                std::cout << "\n===== Withdraw from Account: " << account->getAccountId() << " =====\n";
                                std::cout << "Current Balance: " << std::fixed << std::setprecision(2) << account->getBalance() << "\n";
                                std::cout << "Enter amount to withdraw: ";
                                double amount = getValidAmount();
                                std::cout << "Add note (optional): ";
                                std::string note;
                                std::getline(std::cin, note);
                                account->withdraw(amount, note);
                                std::cout << "Press Enter to continue...";
                                std::cin.get();
                                break;
                            }
                            case 3: {
                                std::cout << "\n===== Transfer from Account: " << account->getAccountId() << " =====\n";
                                std::cout << "Current Balance: " << std::fixed << std::setprecision(2) << account->getBalance() << "\n";
                                std::cout << "Enter destination account ID: ";
                                std::string destId;
                                std::getline(std::cin, destId);
                                if (!validateAccountId(destId)) {
                                    std::cout << "Error: Invalid account ID format (e.g., 6221-XXXX-XXXX-XXXX).\n";
                                    std::cout << "Press Enter to continue...";
                                    std::cin.get();
                                    break;
                                }
                                BankAccount* destAccount = findAccount(destId);
                                if (!destAccount) {
                                    std::cout << "Error: Destination account not found.\n";
                                    std::cout << "Press Enter to continue...";
                                    std::cin.get();
                                    break;
                                }
                                std::cout << "Enter amount to transfer: ";
                                double amount = getValidAmount();
                                std::cout << "Add note (optional): ";
                                std::string note;
                                std::getline(std::cin, note);
                                account->transferTo(destAccount, amount, note);
                                std::cout << "Press Enter to continue...";
                                std::cin.get();
                                break;
                            }
                            case 4: {
                                std::cout << "\nTransaction History:\n";
                                std::cout << std::left << std::setw(10) << "ID"
                                          << std::setw(20) << "Date"
                                          << std::setw(15) << "Type"
                                          << std::setw(10) << "Amount"
                                          << "Note\n";
                                std::cout << std::string(65, '-') << "\n";

                                for (const auto& t : account->getTransactions()) {
                                    std::cout << std::left << std::setw(10) << t.getTransactionId()
                                              << std::setw(20) << t.getDate()
                                              << std::setw(15) << t.getType()
                                              << std::fixed << std::setprecision(2) << std::setw(9) << t.getAmount()
                                              << t.getNote() << "\n";
                                }
                                std::cout << "Press Enter to continue...";
                                std::cin.get();
                                break;
                            }
                            case 5: {
                                std::time_t now = std::time(nullptr);
                                std::stringstream ss;
                                ss << "transactions_" << account->getAccountId() << "_" << std::put_time(std::localtime(&now), "%Y-%m-%d") << ".txt";
                                account->generateReport(ss.str());
                                std::cout << "Press Enter to continue...";
                                std::cin.get();
                                break;
                            }
                        }
                    }
                }
                break;
            }
            case 3:
                displayAllAccounts();
                std::cout << "Press Enter to return to main menu...";
                std::cin.get();
                break;
            case 4:
                generateGlobalReport();
                std::cout << "Press Enter to return to main menu...";
                std::cin.get();
                break;
        }
    }
}

} // namespace BankingSystem